﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Xml;
using System.Data;

/*
	Class to represent the central configuration document
	Exposes the Xml docs, defines the tags to be used and 
	creates nodes etc.
	
	Use singleton pattern and have a static instance object 

	10/01/2012 - changed namespace to emphasize the pattern
	04/07/2012 - add official tag for connection which permits changes.
*/

namespace DbView.Resources
{
	public class ConfigDoc : XmlDocument
	{
		public const String CFGELM_DOC = "DBVIEW_CONFIG";

		public const String CFGELM_CONNECTIONS = "Connections";
		public const String CFGELM_CONNECTION = "Connection";
		public const String CFGELM_FAVOURITES = "Favourites";
		public const String CFGELM_FAVOURITE = "Favourite";
		public const String CFGELM_VIEWLINKS = "Links";
		public const String CFGELM_VIEWLINK = "Link";
		public const String CFGELM_VIEWDEFS = "Views";
		public const String CFGELM_VIEWDEF = "View";
		public const String CFGELM_SETTINGS = "Settings";
		public const String CFGELM_NS_UI = "ui"; // settings namespace for ui
	
		// view defs
		public const String CFGVIEWDEF_VIEWCOL = "ViewCol";
		public const String CFGVIEWDEF_SORTCOL = "SortCol";
		public const String CFGVIEWDEF_FILTERCOL = "FilterCol";
		public const String CFGVIEWDEF_NUMFILTERCOL = "NumFilterCol";
		public const String CFGVIEWDEF_EXTRAINFO = "Extra";
		public const String CFGVIEWDEF_FILTER = "Filter";
		public const String CFGVIEWDEF_JOINCLAUSE = "Join";
		public const String CFGATT_UNTESTED = "Unverified";
		
		public const String CFGSETTING_TABMODE = "Tabmode";
		public const String CFGSETTING_FASTFIND = "FastFind";

		public const String CFGATT_CONN = "conn";
		public const String CFGATT_LASTUSED = "lastused";
		public const String CFGATT_LASTUSEDDRIVER = "lastdriver";
		public const String CFGATT_ENCRYPTION = "encryption";
		public const String CFGATT_NAME = "name";
		public const String CFGATT_DBOBJNAME = "name";
		public const String CFGATT_DBOBJTTYPE = "objtype";
		public const String CFGATT_TABLE = "table";
		public const String CFGATT_DRIVER = "drv";
		public const String CFGATT_SERVER = "srvr";
		public const String CFGATT_DATABASE = "db";
		public const String CFGATT_USER = "uid";
		public const String CFGATT_PASSWORD = "pwd";
		public const String CFGATT_ENCRYTED = "encoded";
		public const String CFGATT_DEV = "dev";  // basis for allaw changes ie DEV database
	
		public static ConfigDoc Instance
		{
			get 
			{
				if (theConfig == null)
					theConfig = new ConfigDoc();
				return theConfig;
			}
		}
		
		ConfigDoc()
		{
			PortableMode = false;
		}
		
		// Since load and save seem to be asymmetrical (load once at start, save many times)
		// this will expose the file used for the load.
		public String FileName { get; set; }
		
		public bool PortableMode {get; set; }

		// careful with name Load() is used by the base class
		public void LoadConfig(String fileName)
		{
			try 
			{
				FileName = fileName; // store the name for access by others
				this.Load(fileName);
				CheckVersion();
			}
			catch (System.IO.FileNotFoundException)
			{
				// Add root element
				XmlNode node = this.CreateElement(CFGELM_DOC);
				this.AppendChild(node);
			}
		}
	
		// careful with name Save() is used by the base class
		// original required file name. Still exposed in case copying/backup required
		// this used to copy the config file to the portable application area.
		public void SaveConfig(String fileName)
		{
			this.Save(fileName);
		}

		// New/Prefered: use filename given when loading the config.
		public void SaveConfig()
		{
			this.Save(FileName);
		}
		
		// note send in the group name: appends a new element with the correct name
		public XmlNode CreateNewRecordInGroup(String groupElementName)
		{
			String newElementName = (groupElementName == CFGELM_FAVOURITES) ? CFGELM_FAVOURITE :
									(groupElementName == CFGELM_VIEWLINKS) ? CFGELM_VIEWLINK :
									(groupElementName == CFGELM_VIEWDEFS) ? CFGELM_VIEWDEF :
									(groupElementName == CFGELM_CONNECTIONS) ? CFGELM_CONNECTION : "";
			if (newElementName.Length == 0)
				throw new Exception(String.Format("{0} not a config group element name",groupElementName)); 

			XmlNode node = this.CreateElement(newElementName);
			XmlNode groupNode = GetGroupNode(groupElementName);
			groupNode.AppendChild(node);
			return node;
		}

		public XmlNode CreateNewChild(XmlNode parent, String childElementName)
		{
			XmlNode node = this.CreateElement(childElementName);
			parent.AppendChild(node);
			return node;
		}

		public void RemoveRecord(XmlNode node)
		{
			XmlNode parent = node.ParentNode;
			if (parent != null)
				parent.RemoveChild(node);
		}

		public XmlNode Connections
		{
			get { return GetGroupNode(CFGELM_CONNECTIONS); }
		}

		public XmlNode Views
		{
			get { return GetGroupNode(CFGELM_VIEWDEFS); }
		}

		public XmlNode Viewlinks
		{
			get { return GetGroupNode(CFGELM_VIEWLINKS); }
		}

		public XmlNode Favourites
		{
			get { return GetGroupNode(CFGELM_FAVOURITES); }
		}

		XmlNode GetGroupNode(String groupElementName)
		{
			String xpath = String.Format("/{0}/{1}", CFGELM_DOC, groupElementName);
			XmlNode node = this.SelectSingleNode(xpath);
			if (node == null)
			{
				node = this.CreateElement(groupElementName);
				this.DocumentElement.AppendChild(node);
			}
			return node;
		}
		
		public void AddAttribute(XmlNode node, String attName, String attVal)
		{
			XmlAttribute att = node.Attributes[attName];
			if (att == null)
			{
				att = this.CreateAttribute(attName);
				node.Attributes.Append(att);
			}
			att.Value = attVal;
		}
		public void AddAttribute(XmlNode node, String attName, bool value)
		{
			// maps bool settings to Y/N in config. See decode function below.
			AddAttribute(node, attName, (value) ? "Y" : "N");
		}
	
		public String AttributeValue(XmlNode node, String attName, String defaultVal)
		{
			XmlAttribute att = node.Attributes[attName];
			if (att == null) return defaultVal;
			return att.Value;
		}
		
		public String AttributeValue(XmlNode node, String attName)
		{
			return AttributeValue(node, attName, "");
		}

		public bool AttributeValue(XmlNode node, String attName, bool defaultVal)
		{
			XmlAttribute att = node.Attributes[attName];
			if (att == null || att.Value.Length == 0) return defaultVal; // length 0 should equate to false? Don't care. I'll avoid the issue
			
			char testVal = att.Value[0];
			// allows any non-zero number or Y, Yes, T True
			// all else is false.
			return ("123456789YyTt".IndexOf(testVal) >= 0) ? true : false;
		}

		public String ElementText(XmlNode node, String elementName)
		{
			XmlNode tgtNode = node.SelectSingleNode(elementName);
			if (tgtNode == null) return "";
			return tgtNode.InnerText;
		}

		public List<String> Elements(XmlNode node, String elementName)
		{	
			List<String> data = new List<string>();
			XmlNodeList nodes = node.SelectNodes(elementName);
			foreach (XmlNode n in nodes)
				data.Add(n.InnerText);
			return data;
		}
	
		public void RemoveTempViews()
		{
			String xpath = String.Format("//{0}/{1}[@Temp]", ConfigDoc.CFGELM_VIEWDEFS, ConfigDoc.CFGELM_VIEWDEF);
			XmlNodeList killNodes = this.SelectNodes(xpath);
			foreach (XmlNode killNode in killNodes)
				this.RemoveRecord(killNode);
		}

		// Helper function to create several levels of children
		// supports aaa, aaa/bbb, aaa/bbb/ccc etc.
		public static XmlNode CreateSettingNode(XmlNode parent, String path)
		{
			XmlDocument doc = parent.OwnerDocument;
			String[] paths = path.Split('/');
			XmlNode immediateParent = parent;
			XmlNode newChild = null;
			foreach (String onePath in paths)
			{
				newChild = immediateParent.SelectSingleNode(onePath);
				if (newChild == null)
				{
					newChild = doc.CreateElement(onePath);
					// add to end of config.
					immediateParent.AppendChild(newChild);
				}
				immediateParent = newChild;
			}
			return newChild;
		}

		static ConfigDoc theConfig = null;

		////////////////////////////////////////////////////////////////////////////////////////////////////
		// invoked on evry open so keep the check short
		private void CheckVersion()
		{
			// we know what the current version is so we should apply version upgrades step-by-step
			// version in root
			String version = AttributeValue(this.DocumentElement, "VERSION", "");
			if (version.Length == 0)
				version = "1.2";
			// pattern for upgrade
			// test for oldest version
			if (version == "1.2")
			{
				// Upgrade(0);
				// ie. default settings.
				version = "1.3";
			}
			// carry on when necessary
			AddAttribute(this.DocumentElement, "VERSION", version); 
		}
	}
}
